package ru.hh.team02.chat.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import ru.hh.team02.chat.server.MessageListeners.AdministratorConsole;
import ru.hh.team02.chat.server.MessageListeners.MessageListener;
import ru.hh.team02.chat.server.SendMessage.Message;
import ru.hh.team02.chat.server.SendMessage.SendingService;
import ru.hh.team02.chat.server.client.ClientRegistry;

/*
 * Dummy client for testing purposes.
 * But will turn into a real client class.
 */
public class Client implements Runnable {
	static private Set<MessageListener> messageListeners = new HashSet<MessageListener>();
	
	static {
		messageListeners.add(new AdministratorConsole());
	}
	
	private String name;
	private String room;
	private ReadWriteLock roomLock = new ReentrantReadWriteLock();
	private volatile boolean isExited;

	private StringSocket requestSocket;

	private InputStreamReader converter = new InputStreamReader(System.in);
	private BufferedReader consoleIn = new BufferedReader(converter);

	public Client(String name) {
		setName(name);
	}

	public Client(String name, String room) {
		this(name);
		setRoom(room);
	}

	public static void main(final String args[]) {
		Client client = new Client("Client1");
		client.run();
	}

	public void run() {
		try {
			requestSocket = new StringSocket(new Socket("localhost", 2004));
			System.out.println("connected to localhost in port 2004");

			requestSocket.openStreams();

			String message = "";
			do {
				if (System.in.available() > 0) {
					message = consoleIn.readLine();
					requestSocket.write(message);
				}

				if (requestSocket.available()) {
					handleIncomingMessage(requestSocket.read());
				}
			} while (!message.equals("bye"));
		} catch (UnknownHostException unknownHost) {
			System.err.println("You are trying to connect to an unknown host!");
		} catch (IOException ioException) {
			ioException.printStackTrace();
		} finally {
			if (requestSocket != null)
				requestSocket.close();
			closeConsoleStreams();
		}
	}

	private void handleIncomingMessage(String message) {
		System.out.println(message);
	}

	private void closeConsoleStreams() {
		try {
			consoleIn.close();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				converter.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public String getName() {
		return name;
	}

	/**
	 * Set the client name. This method should be used only once on client
	 * initialization. That is why it is made private. Because in this
	 * implementation it just changes the private "name" field of the client. If
	 * the name is to be changed in the middle of the program run, this method
	 * should also make ClientRegistry reflect the changes, which is not
	 * implemented at the moment.
	 * 
	 * @param name
	 *            Client's name
	 */
	private void setName(String name) {
		this.name = name;
	}

	/**
	 * Returns the client's room name.
	 * 
	 * This method uses ReadWriteLock in reading mode. When operations on room
	 * name change is going on, it will wait. But readings are simultaneous.
	 * 
	 * @return Room of the client
	 */
	public String getRoom() {
		try {
			roomLock.readLock().lock();
			return room;
		} finally {
			roomLock.readLock().unlock();
		}
	}

	/**
	 * Set the room client field.
	 * 
	 * @param room
	 *            Room name.
	 */
	private void setRoom(String room) {
		try {
			roomLock.writeLock().lock();
			this.room = room;
		} finally {
			roomLock.writeLock().unlock();
		}
	}

	/**
	 * Change the room of the client. It will call all the necessary methods of
	 * the ClientRegistry.
	 * 
	 * @param newRoom
	 *            The name of a new room
	 * @return <code>true</code> if the room has been successfully changed;
	 *         <code>false</code> if the newRoom with the name provided doesn't
	 *         exist.
	 */
	public boolean changeRoom(String newRoom) {
		try {
			roomLock.writeLock().lock();
			String oldRoom = this.room;
			if (!ClientRegistry.changeRoom(this, oldRoom, newRoom)) {
				return false;
			}
			setRoom(newRoom); // this method used roomLock on writing, still it
								// is not a problem, as the same thread
			// should pass the same writeLock more than once without problems
			// See:
			// http://www.mc3dviz.com/openinventor-forum/showpost.php?p=923&postcount=2
			return true;
		} finally {
			roomLock.writeLock().unlock();
		}
	}

	/**
	 * Proliferate the message addressed to the room to the final recipients and
	 * put all the generated messages in the sending queue.
	 * 
	 * @param text
	 *            Message text
	 */
	public void sendMessageToRoom(String text) {
		Set<Client> recepients = ClientRegistry.getClientsFromRoom(this
				.getRoom());
		for (Client recepient : recepients) {
			SendingService.sendMessage(text, recepient);
		}
	}

	public void sendMessageToClientComputer(Message message) {
		// TODO Yan, please, implement it, as I don't fully understand your code
		// dealing with sockets.
	}

	/**
	 * Get the status if the client has already exited. This method may come in
	 * handy when sending the message to the client computer (as the message has
	 * been waiting in a sending queue and the client may already have exited).
	 * 
	 * @return boolean exit status.
	 */
	public boolean hasExited() {
		return isExited;
	}
	
	private void sendMessageToListeners(String message) {
		for (MessageListener listener: messageListeners) {
			listener.onMessage(message);
		}
	}

}
